package com.sse.lb.colas;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Implementacion del componente para el manejo de colas.
 * 
 * @author Paul
 * 
 */
public class LBManejadorColas implements ILBManejadorColas {

	/** Instancia singleton del manejador de colas. */
	private static final LBManejadorColas instance = new LBManejadorColas();

	/** Cantidad maxima de threads a usar para encolamiento de tramas. */
	private static final int QUEUE_TASK_THREADS = 30;

	/** Lista doble que respalda la cola observable. */
	// private Deque<byte[]> colaTramas;
	private List<byte[]> colaTramas;

	/** Thread pool usado para el encolamiento de tramas. */
	private ExecutorService queueTaskThreadPool = null;

	/**
	 * mapa que referencia las tramas y los sistemas a los que han sido
	 * enviadas.
	 */
//	private Map<Long, Set<byte[]>> tramasEnviadas = null;
	private Map<Long, Set<ByteBuffer>> tramasEnviadas = null;

	private LBManejadorColas() {
		// colaTramas = new LinkedList<byte[]>();
		colaTramas = Collections.synchronizedList(new LinkedList<byte[]>());
		tramasEnviadas = new ConcurrentHashMap<Long, Set<ByteBuffer>>();
		queueTaskThreadPool = Executors.newFixedThreadPool(QUEUE_TASK_THREADS);
	}

	public static LBManejadorColas getInstance() {
		return instance;
	}

	@Override
	public void encolarTramaAsincrona(byte[] data) {
		QueueTask q = new QueueTask(data, false);
		this.queueTaskThreadPool.execute(q);

	}

	@Override
	public void encolarTramaAsincronaPrioridad(byte[] data) {
		// TODO: Es posible que sea necesario manejar un pool de objetos para
		// estas
		// tareas, solo si llega a ser necesario.
		QueueTask q = new QueueTask(data, true);
		this.queueTaskThreadPool.execute(q);

	}

	@Override
	public byte[] obtenerTrama() {
		// return colaTramas.poll();
		// return ((Deque<byte[]>)colaTramas).poll();
		if (colaTramas.isEmpty()) {
			return null;
		} else {
			return colaTramas.remove(0);
		}
	}

	@Override
	public void agregarTramaASistema(Long idSistema, byte[] trama) {

		Set<ByteBuffer> tramas = this.tramasEnviadas.get(idSistema);

		if (tramas == null) {
			tramas = new HashSet<ByteBuffer>();
		}

		tramas.add(ByteBuffer.wrap(trama));
		this.tramasEnviadas.put(idSistema, tramas);
//System.out.println("Se adiciono trama. tam = " + tramas.size());
	}

	@Override
	public void removerTramaSistema(Long idSistema, byte[] trama) {
		Set<ByteBuffer> tramas = this.tramasEnviadas.get(idSistema);

		if (tramas != null) {
			tramas.remove(ByteBuffer.wrap(trama));
			this.tramasEnviadas.put(idSistema, tramas);
		}
//System.out.println("Se elimino trama. tam = " + this.tramasEnviadas.get(idSistema).size());
	}

	/**
	 * Obtiene y remueve todas las tramas asociadas a un sistema
	 * @param idSistema
	 * @return
	 */
	public List<byte[]>  removerTodasTramasSistema(Long idSistema){	
		
		Set<ByteBuffer> tramas = this.tramasEnviadas.remove(idSistema);
		
		List<byte[]> lista = new ArrayList<byte[]>();
		
		for(ByteBuffer b : tramas){
			lista.add(b.array());
		}
		
		return lista;	
	}
	
	
	public void finalizarManejador() {
		this.queueTaskThreadPool.shutdown();

	}

	public static void main(String[] args) {

		final ILBManejadorColas m = LBManejadorColas.getInstance();

		byte[][] array = new byte[][] { { 0, 1, 0, 0 }, { 1, 2, 0, 0 },
				{ 1, 1, 1, 1 }, { 1, 0, 0, 1 }, { 1, 0, 20, 3 }, };

		for (int i = 0; i < array.length; i++) {
			m.encolarTramaAsincrona(array[i]);
			m.encolarTramaAsincrona(array[i]);
			m.encolarTramaAsincrona(array[i]);
		}

		byte[] data = m.obtenerTrama();
		do {
			System.out.println("Sacando de la cola: " + data);
		} while ((data = m.obtenerTrama()) != null);

		m.finalizarManejador();

	}

	// public static void main(String[] args) {
	//
	// final IManejadorColas m = ManejadorColas.getInstance();
	//
	// byte[][] array = new byte[][] {
	// {0,1,0,0},
	// {1,2,0,0},
	// {1,1,1,1},
	// {1,0,0,1},
	// {1,0,20,3},
	// };
	//
	// m.suscribirProcesadorAlertas(new Observer() {
	// @Override
	// public void update(Observable o, Object arg) {
	// System.out.println("Alerta: "+m.obtenerAlerta());
	// }
	// });
	//
	// m.suscribirProcesadorEventos(new Observer() {
	//
	// @Override
	// public void update(Observable o, Object arg) {
	// System.out.println("Evento: "+m.obtenerEvento());
	//
	// }
	// });
	//
	// for (int i = 0; i < array.length; i++) {
	// m.encolarAlerta(array[i]);
	// m.encolarEvento(array[i]);
	// m.encolarEvento(array[i]);
	//
	// }
	//
	//
	// }

	/**
	 * Clase que define una tarea de encolamiento de tramas.
	 */
	private class QueueTask implements Runnable {

		/** Indica si la trama se debe encolar con prioridad. */
		private boolean prioritaria;

		/** Informacion a encolar. */
		private byte[] data;

		public QueueTask(byte[] data, boolean prioritaria) {
			this.data = data;
			this.prioritaria = prioritaria;
		}

		@Override
		public void run() {
			// System.out.println("Thread: " + Thread.currentThread()
			// + " LB-Encolado asincrono. ");

			if (prioritaria) {
				// colaTramas.addFirst(data);
				// ((Deque<byte[]>)colaTramas).addFirst(data);
				colaTramas.add(0, data);
			} else {
				colaTramas.add(data);
			}
		}

	}
	
	public int obtenerTamCola(){
		return colaTramas.size();
	}

}
